﻿@inject ISearchService searchService
@inject IUserConfigService configService
@inject IUserStatusService statusService
@inject IUserBasketService basketService
@inject IImageCacheService imageCache
@inject ILogger<ImageGrid> logger
@inject IJSRuntime JsRuntime
@implements IDisposable
@inherits ImageGridBase

<LocalFileExporter @ref="FileExporter"/>

<div class="imagegrid" id="scroll-area">
    @if ( !gridImages.Any() )
    {
        @if( NoResultsFound )
        {
            <div class="no-results">
                <text>No images were found that match the filter:</text>
                <text>@searchService.SearchBreadcrumbs</text>
            </div>
        }
        else
        {
            <ProgressSpinner ProgressText="Loading images..."/>
        }
    }
    else
    {
        var allImages = 0;
        foreach ( var grouping in GroupedImages )
        {
            if ( !string.IsNullOrEmpty(grouping.Key) )
            {
                <div class="damselfly-imageseparator">
                    <div class="damselfly-imageseparatortitle">
                        @grouping.Key
                    </div>
                    <div class="damselfly-imageseparatorbuttons">
                        <button @onclick="async () => { await AddGroupToBasket(grouping); }" class="btn btn-primary damselfly-imageseparatorbutton">
                            <i class="fas fa-shopping-basket"/>
                            &nbsp;Add
                        </button>
                        <button @onclick="async () => { await RemoveGroupFromBasket(grouping); }" class="btn btn-primary damselfly-imageseparatorbutton">
                            <i class="fas fa-shopping-basket"/>
                            &nbsp;Remove
                        </button>
                        <button @onclick="() => { SelectGroup(grouping); }" class="btn btn-primary damselfly-imageseparatorbutton">
                            &nbsp;Select
                        </button>
                        <button @onclick="() => { DeselectGroup(grouping); }" class="btn btn-primary damselfly-imageseparatorbutton">
                            &nbsp;De-select
                        </button>
                        @if ( FileExporter != null && FileExporter.IsDesktopHosted )
                        {
                            <button @onclick="async () => { await FileExporter.ExportImagesToLocalFilesystem(grouping.Images); }" class="btn btn-primary">
                                <i class="fas fa-download"/>
                                &nbsp;Save Locally
                            </button>
                        }
                    </div>
                </div>
            }

            foreach ( var image in grouping.Images )
            {
                var info = new SelectionInfo { image = image, index = allImages++ };

                <GridImage CurrentImage=@info.image NavContext=@NavigationContexts.Search GridImageSize=@CurrentGridSize
                           OnClick="@(async e => { await ToggleSelected(e, info); })"/>
            }
        }

        if ( !endOfImages )
        {
            <div class="damselfly-moreimages" id="list-end">
                <ProgressSpinner ProgressText="Loading..."/>
            </div>
        }
        else
        {
            <div id="snackbar" class="@toastClass">No more images to show.</div>
        }
    }
</div>

<ScrollMonitor @ref="ScrollMonitor" ScrollElementId="scroll-area" ScrollConfigName="ImageScrollTop"/>


@code {
    private LocalFileExporter FileExporter;
    const int imagesPerPage = DamselflyContants.PageSize;
    private bool NoImagesSelected => !selectionService.Selection.Any();
    bool endOfImages = false;
    string toastClass = string.Empty;
    private ScrollMonitor ScrollMonitor;
    CancellationTokenSource cancelTokenSource = new();
    CancellationToken? token = null;

    [Parameter] public GridImageSize CurrentGridSize { get; set; }

    private bool NoResultsFound { get; set; } = false;

    protected override void OnInitialized()
    {
        token = cancelTokenSource.Token;
        
        base.OnInitialized();
    }

    async Task AddGroupToBasket(ImageGrouping grouping)
    {
        var imageIds = grouping.Images.Select(x => x.ImageId).ToList();
        await basketService.SetImageBasketState(true, imageIds);
        statusService.UpdateStatus($"{imageIds.Count()} images added to the basket");
    }

    async Task RemoveGroupFromBasket(ImageGrouping grouping)
    {
        var imageIds = grouping.Images.Select(x => x.ImageId).ToList();
        await basketService.SetImageBasketState(false, imageIds);
        statusService.UpdateStatus($"{imageIds.Count()} images removed from the basket");
    }

    void SelectGroup(ImageGrouping grouping)
    {
        selectionService.SelectImages(grouping.Images);
    }

    void DeselectGroup(ImageGrouping grouping)
    {
        selectionService.DeselectImages(grouping.Images);
    }

    protected override void OnParametersSet()
    {
        ChangeGridImageSize(CurrentGridSize);
        base.OnParametersSet();
    }

    private List<ImageGrouping> GroupedImages
    {
        get
        {
            // TODO: Order by
            if ( searchService.Grouping == GroupingType.Folder )
            {
                var result = gridImages.GroupBy(x => x.Folder.Path);

                if ( searchService.SortOrder == SortOrderType.Descending )
                    result = result.OrderByDescending(x => x.Key);
                else
                    result = result.OrderBy(x => x.Key);

                return result.Select(x => new ImageGrouping { Key = x.Key, Images = x.ToList() })
                    .ToList();
            }

            if ( searchService.Grouping == GroupingType.Date )
            {
                var result = gridImages.GroupBy(x => x.SortDate.Date);

                if ( searchService.SortOrder == SortOrderType.Descending )
                    result = result.OrderByDescending(x => x.Key);
                else
                    result = result.OrderBy(x => x.Key);

                return result.Select(x => new ImageGrouping { Key = x.Key.ToString("dddd, dd MMMM yyyy"), Images = x.ToList() })
                    .ToList();
            }

            return new List<ImageGrouping> { new() { Key = null, Images = gridImages } };
        }
    }

    private void ChangeGroupType(GroupingType newType)
    {
        searchService.Grouping = newType;
        // Logging.Log($"Grouping changed to {searchService.Grouping}");
        StateHasChanged();
    }

    private void ChangeSortOrder(SortOrderType newType)
    {
        searchService.SortOrder = newType;
        // Logging.Log($"Sort order changed to {searchService.SortOrder}");
        StateHasChanged();
    }

    protected void ChangeGridImageSize(GridImageSize newSize)
    {
        configService.Set(ConfigSettings.GridImageSize, newSize.ToString());
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if ( firstRender )
        {
            selectionService.OnSelectionChanged += SelectedImagesChanged;
            searchService.OnSearchQueryChanged += SearchQueryChanged;
            searchService.OnSearchResultsChanged += SearchResultsChanged;
            basketService.OnBasketChanged += BasketChanged;
            ScrollMonitor.OnScrollPositionChanged += SaveScrollState;
            FileExporter.OnChange += StateHasChanged;

            var initialLoadCount = Math.Max(configService.GetInt(ConfigSettings.LoadedImages, imagesPerPage), imagesPerPage);

            if ( initialLoadCount > searchService.SearchResults.Count )
                await searchService.LoadMore(initialLoadCount);
            else
                await AddImagesToGrid(true, cancelTokenSource.Token);

            await JsRuntime.InvokeVoidAsync("InfiniteScroll.Init", "scroll-area", "list-end", DotNetObjectReference.Create(this));
        }
    }

    protected void SelectedImagesChanged()
    {
        StateHasChanged();
    }

    protected void ImagesChanged( int id )
    {
        StateHasChanged();
    }

    public void Dispose()
    {
        selectionService.OnSelectionChanged -= SelectedImagesChanged;
        searchService.OnSearchQueryChanged -= SearchQueryChanged;
        searchService.OnSearchResultsChanged -= SearchResultsChanged;
        FileExporter.OnChange -= StateHasChanged;
        basketService.OnBasketChanged -= BasketChanged;
        ScrollMonitor.OnScrollPositionChanged -= SaveScrollState;
    }

    protected void BasketChanged(BasketChanged changeType)
    {
        StateHasChanged();
    }

    protected void SearchResultsChanged(SearchResponse response)
    {
        // If we've tried to load data and there isn't any more, show the toast.
        toastClass = endOfImages ? "show" : string.Empty;
        
        _ = AddImagesToGrid(response.MoreDataAvailable, cancelTokenSource.Token);
    }

    private void ResetSearch()
    {
        // Cancel the old request, and start a new one
        cancelTokenSource.Cancel();
        cancelTokenSource.Dispose();
        cancelTokenSource = new CancellationTokenSource();
    }

    private void SearchQueryChanged()
    {
        // Abort any in-progress search results from being rendered
        ResetSearch();
        
        selectionService.ClearSelection();
        gridImages.Clear();
        endOfImages = false;
        StateHasChanged();
    }

    private void SaveScrollState(int scrollTop)
    {
        configService.SetForUser(ConfigSettings.LoadedImages, gridImages.Count.ToString());
    }

    [JSInvokable]
    // Debugging assistant to help us differentiate between JS calls and other data loads
    public async Task LoadMoreData()
    {
        logger.LogTrace("Javscript callback triggered to load more data.");

        await searchService.LoadMore();
    }

    private async Task AddImagesToGrid(bool moreDataAvailable, CancellationToken token)
    {
        NoResultsFound = true;
        
        if ( searchService.SearchResults.Any() )
        {
            NoResultsFound = false;

            // Be careful, as searchResults could change underneath us....
            var chunks = searchService.SearchResults
                                               .Chunk(25)
                                               .ToList();
            
            foreach( var set in chunks )
            {
                StateHasChanged();

                var toAdd = set.ExceptBy(gridImages.Select(x => x.ImageId), x => x).ToList();
                var images = await imageCache.GetCachedImages(toAdd);

                // Check if we had a cancellation request while we pulled back the images. If so, abort
                if( token.IsCancellationRequested )
                    return;

                if(images.Any())
                {
                    gridImages.AddRange(images);
                    configService.Set(ConfigSettings.LoadedImages, gridImages.Count.ToString());
                }
            }
        }

        // Flag the 'more' div if we loaded at least as many as we requested.
        endOfImages = !moreDataAvailable;
        StateHasChanged();
    }

}